/*
 *  SoundElement.h
 *  SoundController0002
 *
 *  Created by jiro on 10/06/28.
 *  Copyright 2010 jiro music. All rights reserved.
 *
 */

#pragma once

#include "Constants.h"
#include "Line.h"
#include "WaveTable.h"
#include <vector>

namespace sc 
{
	template< class AudioData >
	class SoundElementFactory;
	
	template< class AudioData >
	class SoundElement
	{

		friend class SoundElementFactory< AudioData >;
		
		friend class SoundElementTester;

	private:

		double attackEndTime;

		const Line* attackDynamicsLine;

		double decayEndTime;

		const Line* decayDynamicsLine;
		
		double sustainEndTime;

		const Line* sustainDynamicsLine;

		double releaseEndTime;
		
		const Line* releaseDynamicsLine;

		const WaveTable* soundTable;
		
		const WaveTable* dynamicsTable;

		AudioData (SoundElement< AudioData >::*getAudioDataFuncPtr)( double time ) const;

	public:
	
		SoundElement() {}

		double GetDuration() const
		{
			return releaseEndTime;
		}

	private:

		mutable double formerReturnData;

		mutable double formerData;

		mutable double formerDynamics;

		mutable double formerTime;
		
	public:

		AudioData GetAudioData( double time ) const
		{
			return (this->*getAudioDataFuncPtr)( time );
		}

		bool operator == ( const SoundElement< AudioData >& rhs ) const
		{
			if ( attackEndTime == rhs.attackEndTime && 
				 attackDynamicsLine == rhs.attackDynamicsLine &&
				 decayEndTime == rhs.decayEndTime && 
				 decayDynamicsLine == rhs.decayDynamicsLine &&
				 sustainEndTime == rhs.sustainEndTime &&
				 sustainDynamicsLine == rhs.sustainDynamicsLine &&
				 releaseEndTime == rhs.releaseEndTime &&
				 releaseDynamicsLine == rhs.releaseDynamicsLine &&
				 soundTable == rhs.soundTable )
			{
				return true;
			}
			return false;
		}

		bool operator != ( const SoundElement< AudioData >& rhs ) const
		{
			return ! ( *this == rhs );
		}
		
		//	for std::set
		bool operator < ( const SoundElement< AudioData >& rhs ) const
		{
			bool isEqual;

			isEqual = attackEndTime == rhs.attackEndTime;
			if ( ! isEqual )
			{
				return attackEndTime < rhs.attackEndTime;
			}

			if ( HasDynamicsTable() ) {

				isEqual = *dynamicsTable == *rhs.dynamicsTable;
				if ( ! isEqual ) {
					return *dynamicsTable < *(rhs.dynamicsTable);
				}

			} else {
				isEqual = attackDynamicsLine == rhs.attackDynamicsLine;
				if ( ! isEqual )
				{
					return attackDynamicsLine < rhs.attackDynamicsLine; 
				}

				isEqual = decayDynamicsLine == rhs.decayDynamicsLine;
				if ( ! isEqual )
				{
					return decayDynamicsLine < rhs.decayDynamicsLine; 
				}

				isEqual = sustainDynamicsLine == rhs.sustainDynamicsLine;
				if ( ! isEqual )
				{
					return sustainDynamicsLine < rhs.sustainDynamicsLine;
				}

				isEqual = releaseDynamicsLine == rhs.releaseDynamicsLine;
				if ( ! isEqual )
				{
					return releaseDynamicsLine < rhs.releaseDynamicsLine;
				}

				isEqual = decayEndTime == rhs.decayEndTime;
				if ( ! isEqual )
				{
					return decayEndTime < rhs.decayEndTime;
				}
				
				isEqual = sustainEndTime == rhs.sustainEndTime;
				if ( ! isEqual )
				{
					return sustainEndTime < rhs.sustainEndTime;
				}
				
				isEqual = releaseEndTime == rhs.releaseEndTime;
				if ( ! isEqual )
				{
					return releaseEndTime < rhs.releaseEndTime;
				}

			}

			isEqual = *soundTable == *rhs.soundTable;
			if ( ! isEqual )
			{
				return *soundTable < *(rhs.soundTable);
			}

			return false;
		}

		bool operator >= ( const SoundElement< AudioData >& rhs ) const
		{
			return ! ( (*this) < rhs );
		}

		double GetAttackEndTime() const
		{
			return attackEndTime;
		}
		
		const Line* const GetAttackDynamicsLine() const
		{
			return attackDynamicsLine;
		}
		
		double GetDecayEndTime() const
		{
			return decayEndTime;
		}
		
		const Line* const GetDecayDynamicsLine() const
		{
			return decayDynamicsLine;
		}

		double GetSustainEndTime() const
		{
			return sustainEndTime;
		}

		const Line* const GetSustainDynamicsLine() const
		{
			return sustainDynamicsLine;
		}
		
		double GetReleaseEndTime() const
		{
			return releaseEndTime;
		}

		const Line* const GetReleaseDynamicsLine() const
		{
			return releaseDynamicsLine;
		}
		
		const WaveTable& GetSoundTable() const
		{
			return *soundTable;
		}
		
		const WaveTable& GetDynamicsTable() const
		{
			return *dynamicsTable;
		}

		double GetFrequency() const
		{
			return soundTable->GetFrequency();
		}

		bool HasDynamicsTable() const 
		{
			if ( dynamicsTable != NULL ) {
				return true;
			}
			return false;
		}

		std::string ToString() const
		{
			std::stringstream ss;
			ss << "SoundElement:\n";
			
			if ( NULL != soundTable ) {
				ss << "\tsoundTable: " << soundTable->ToString() << "\n";
			}
			if ( NULL != dynamicsTable ) {
				ss << "\tdynamicsTable: " << dynamicsTable->ToString() << "\n";
			}
			
			return ss.str();
		}

	private:

		AudioData GetAudioDataUsingDynamicsTable( double time ) const
		{
			AudioData dynamics = (*dynamicsTable)[ time ];
			AudioData sound = (*soundTable)[ time ];
			AudioData data = dynamics * sound;
			return data;
		}

		AudioData GetAudioDataUsingDynamicsLine( double time ) const
		{
			AudioData data = (*soundTable)[ time ];

			if ( 0 <= time && time <= attackEndTime )
			{
				formerTime = time;
				formerData = data;				
				formerDynamics = attackDynamicsLine->GetY( time );
				data = static_cast< AudioData >( attackDynamicsLine->GetY( time ) * data );
				formerReturnData = data;
				return data;
			}
			else if ( attackEndTime < time && time <= decayEndTime )
			{
				data = static_cast< AudioData >( decayDynamicsLine->GetY( time ) * data );
				return data;
			}
			else if ( decayEndTime < time && time <= sustainEndTime )
			{
				data = static_cast< AudioData >( sustainDynamicsLine->GetY( time ) * data );
				return data;
			}
			else if ( sustainEndTime < time && time <= releaseEndTime )
			{
				data = static_cast< AudioData >( releaseDynamicsLine->GetY( time ) * data );
				return data;
			}
			else
			{
				return 0;
			}
		}

		//	set by end time
		SoundElement( double attackEndTime, const Line* const attackDynamicsLine,
					  double decayEndTime, const Line* const decayDynamicsLine,
					  double sustainEndTime, const Line* const sustainDynamicsLine,
					  double releaseEndTime, const Line* const releaseDynamicsLine,
					  const WaveTable& soundTable )
			: attackEndTime( attackEndTime ),
			  attackDynamicsLine( attackDynamicsLine ),
			  decayEndTime( decayEndTime ),
			  decayDynamicsLine( decayDynamicsLine ),
			  sustainEndTime( sustainEndTime ),
			  sustainDynamicsLine( sustainDynamicsLine ),
			  releaseEndTime( releaseEndTime ),
			  releaseDynamicsLine( releaseDynamicsLine ),
			  soundTable( & soundTable ),
			  dynamicsTable( NULL ),
			  getAudioDataFuncPtr( & SoundElement::GetAudioDataUsingDynamicsLine )
		{}
		
		//	set by relative time
		SoundElement( bool setByRelativeTime,
					  double attackTime, const Line* const attackDynamicsLine,
					  double decayTime, const Line* const decayDynamicsLine,
					  double sustainTime, const Line* const sustainDynamicsLine,
					  double releaseTime, const Line* const releaseDynamicsLine,
					  const WaveTable& soundTable )
			: attackDynamicsLine( attackDynamicsLine ),
			  decayDynamicsLine( decayDynamicsLine ),
			  sustainDynamicsLine( sustainDynamicsLine ),
			  releaseDynamicsLine( releaseDynamicsLine ),
			  soundTable( & soundTable ),
			  dynamicsTable( NULL ),
			  getAudioDataFuncPtr( & SoundElement::GetAudioDataUsingDynamicsLine )
		{
			attackEndTime = attackTime;
			decayEndTime = attackEndTime + decayTime;
			sustainEndTime = decayEndTime + sustainTime;
			releaseEndTime = sustainEndTime + releaseTime;
		}

		SoundElement( const WaveTable& soundTable, const WaveTable& dynamicsTable, double length )
			: attackEndTime( length ),
			  attackDynamicsLine( NULL ),
			  decayEndTime( length ),
			  decayDynamicsLine( NULL ),
			  sustainEndTime( length ),
			  sustainDynamicsLine( NULL ),
			  releaseEndTime( length ),
			  releaseDynamicsLine( NULL ),
			  soundTable( & soundTable ),
			  dynamicsTable( & dynamicsTable ),
			  getAudioDataFuncPtr( & SoundElement::GetAudioDataUsingDynamicsTable )
		{}

	};
	
	typedef SoundElement< float > SoundElement32;

	typedef SoundElement< double > SoundElement64;

}